package com.black.gateway.filter;

import com.black.gateway.permission.service.PermissionHandlerService;
import com.black.gateway.util.JwtUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;


/**
 * @author linwenfeng
 */
@Component
@Slf4j
@Order(0)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AuthenticationFilter implements GlobalFilter {

    private final JwtUtil jwtUtil;

    private final PermissionHandlerService permissionHandler;

    private final static String WITHOUT_AUTHORIZATION_MESSAGE = "{\"code\":401,\"message\":\"Without Authorization\",\"result\":null}";

    private final static String TOKEN_INVALID_MESSAGE = "{\"code\":401,\"message\":\"Token Invalid Please ReLogin !\",\"result\":null}";

    private final static String TOKEN_ERROR_MESSAGE = "{\"code\":401,\"message\":\"Token Error\",\"result\":null}";

    private final static String WITHOUT_PERMISSION_MESSAGE = "{\"code\":403,\"message\":\"Without Permission\",\"result\":null}";

    private final static String SUCCESS_MESSAGE = "{\"code\":200,\"message\":\"SUCCESS\",\"result\":null}";

    private final static String COM_CODE_FIELD = "com_code";

    private final static String USER_ID_FIELD = "user_id";

    private final static String ICON_URL_FIELD = "icon_url";

    private final static String USERNAME_FIELD = "user_name";

    /**
     * 鉴权拦截
     * 1.非需鉴权的URI放行
     * 如登录URI
     * 2.需要鉴权的URI进行token校验
     * 校验内容有：
     * 合法性
     * 有效性
     * 方法有：
     * JWT校验
     * 中间件校验：如redis
     *
     * @param chain 过滤链
     * @return void
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        log.info("请求URI:{}", path);
        if (isNotNeedLoginPath(path)) {
            return chain.filter(exchange);
        }
        String accessToken = this.extractToken(exchange.getRequest());
        if (accessToken == null) {
            return responseRs(exchange, HttpStatus.UNAUTHORIZED, WITHOUT_AUTHORIZATION_MESSAGE);
        } else {
            try {
                Claims claims = jwtUtil.parseJWT(accessToken);
                String username = (String) claims.get(USERNAME_FIELD);
                Long userId = (Long) claims.get(USER_ID_FIELD);
                if (StringUtils.isEmpty(username) || userId == null) {
                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    return exchange.getResponse().setComplete();
                }
                if (permissionHandler.isRefreshPermissionPath(path)) {
                    permissionHandler.initAllRouterPermissionsData(0);
                    return responseRs(exchange, HttpStatus.OK, SUCCESS_MESSAGE);
                } else if (permissionHandler.isRefreshWithPath(path)) {
                    permissionHandler.initAllWhitePath(0);
                    return responseRs(exchange, HttpStatus.OK, SUCCESS_MESSAGE);
                }
                String permissionPath = getPermissionPath(path);
                if (!StringUtils.isEmpty(permissionPath) && !isHavePermission(permissionPath, userId)) {
                    return responseRs(exchange, HttpStatus.FORBIDDEN, WITHOUT_PERMISSION_MESSAGE);
                }
                // 在此处补传递的信息
                ServerHttpRequest.Builder mutate = exchange.getRequest().mutate();
                mutate.header("x-user-name", username);
                mutate.header("x-user-id", String.valueOf(userId));
                if (claims.get(COM_CODE_FIELD) != null) {
                    mutate.header("x-com-code", (String) claims.get(COM_CODE_FIELD));
                }
                if (claims.get(ICON_URL_FIELD) != null) {
                    mutate.header("x-icon-url", (String) claims.get(ICON_URL_FIELD));
                }
                ServerHttpRequest request = mutate.build();
                return chain.filter(exchange.mutate().request(request).build());
            } catch (Exception exception) {
                String message = "";
                if (exception instanceof ExpiredJwtException) {
                    log.error("token失效");
                    message = TOKEN_INVALID_MESSAGE;
                } else if (exception instanceof SignatureException) {
                    log.error("解密出错", exception);
                    message = TOKEN_ERROR_MESSAGE;
                } else {
                    log.error("出错", exception);
                }
                return responseRs(exchange, HttpStatus.UNAUTHORIZED, message);
            }
        }
    }

    private boolean isNotNeedLoginPath(String path) {
        return permissionHandler.isWhitePath(path);
    }

    private String getPermissionPath(String path) {
        return permissionHandler.getPermissionPath(path);
    }

    private boolean isHavePermission(String path, Long userId) {
        return permissionHandler.isHavePermission(path, userId);
    }

    private String extractToken(ServerHttpRequest request) {
        List<String> strings = request.getHeaders().get("Authorization");
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0).substring("Bearer".length()).trim();
        }
        if (StringUtils.isEmpty(authToken)) {
            strings = request.getQueryParams().get("access_token");
            if (strings != null) {
                authToken = strings.get(0);
            }
        }
        return authToken;
    }

    private Mono<Void> responseRs(ServerWebExchange exchange, HttpStatus httpStatus, String message) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.setStatusCode(httpStatus);
        serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        return serverHttpResponse.writeWith(Flux.just(buffer));
    }

}
